<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>快速入门 - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/intro/quickstart.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/intro/quickstart.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>快速入门</h1>
    <p>本文将从实例的角度，一步步地搭建出一个 Egg.js 应用，让你能快速的入门 Egg.js。</p>
<h2 id="环境准备"><a class="markdown-anchor" href="#环境准备">#</a> 环境准备</h2>
<ul>
<li>操作系统：支持 macOS，Linux，Windows</li>
<li>运行环境：建议选择 <a href="http://nodejs.org" target="_blank" rel="noopener">LTS 版本</a>，最低要求 8.x。</li>
</ul>
<h2 id="快速初始化"><a class="markdown-anchor" href="#快速初始化">#</a> 快速初始化</h2>
<p>我们推荐直接使用脚手架，只需几条简单指令，即可快速生成项目:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i egg-init -g</span><br><span class="line">$ egg-init egg-example --<span class="built_in">type</span>=simple</span><br><span class="line">$ <span class="built_in">cd</span> egg-example</span><br><span class="line">$ npm i</span><br></pre></td></tr></table></figure>
<p>启动项目:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm run dev</span><br><span class="line">$ open localhost:7001</span><br></pre></td></tr></table></figure>
<h2 id="逐步搭建"><a class="markdown-anchor" href="#逐步搭建">#</a> 逐步搭建</h2>
<p>通常你可以通过上一节的方式，使用 <a href="https://github.com/eggjs/egg-init" target="_blank" rel="noopener">egg-init</a> 快速选择适合对应业务模型的脚手架，快速启动 Egg.js 项目的开发。</p>
<p>但为了让大家更好的了解 Egg.js，接下来，我们将跳过脚手架，手动一步步的搭建出一个 <a href="https://github.com/eggjs/examples/tree/master/hackernews" target="_blank" rel="noopener">Hacker News</a>。</p>
<p><strong>注意：实际项目中，我们推荐使用上一节的脚手架直接初始化。</strong></p>
<p><img src="https://cloud.githubusercontent.com/assets/227713/22960991/812999bc-f37d-11e6-8bd5-a96ca37d0ff2.png" alt="Egg HackerNews Snapshoot"></p>
<h3 id="初始化项目"><a class="markdown-anchor" href="#初始化项目">#</a> 初始化项目</h3>
<p>先来初始化下目录结构：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ mkdir egg-example</span><br><span class="line">$ <span class="built_in">cd</span> egg-example</span><br><span class="line">$ npm init</span><br><span class="line">$ npm i egg --save</span><br><span class="line">$ npm i egg-bin --save-dev</span><br></pre></td></tr></table></figure>
<p>添加 <code>npm scripts</code> 到 <code>package.json</code>：</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"name"</span>: <span class="string">"egg-example"</span>,</span><br><span class="line">  <span class="attr">"scripts"</span>: &#123;</span><br><span class="line">    <span class="attr">"dev"</span>: <span class="string">"egg-bin dev"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="编写-controller"><a class="markdown-anchor" href="#编写-controller">#</a> 编写 Controller</h3>
<p>如果你熟悉 Web 开发或 MVC，肯定猜到我们第一步需要编写的是 <a href="../basics/controller.html">Controller</a> 和 <a href="../basics/router.html">Router</a>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/home.js</span></span><br><span class="line"><span class="keyword">const</span> Controller = <span class="built_in">require</span>(<span class="string">'egg'</span>).Controller;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HomeController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> index() &#123;</span><br><span class="line">    <span class="keyword">this</span>.ctx.body = <span class="string">'Hello world'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = HomeController;</span><br></pre></td></tr></table></figure>
<p>配置路由映射：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/router.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; router, controller &#125; = app;</span><br><span class="line">  router.get(<span class="string">'/'</span>, controller.home.index);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>加一个<a href="../basics/config.html">配置文件</a>：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.keys = <span class="xml"><span class="tag">&lt;<span class="name">此处改为你自己的</span> <span class="attr">Cookie</span> 安全字符串&gt;</span>;</span></span><br></pre></td></tr></table></figure>
<p>此时目录结构如下：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">egg-example</span><br><span class="line">├── app</span><br><span class="line">│   ├── controller</span><br><span class="line">│   │   └── home.js</span><br><span class="line">│   └── router.js</span><br><span class="line">├── config</span><br><span class="line">│   └── config.default.js</span><br><span class="line">└── package.json</span><br></pre></td></tr></table></figure>
<p>完整的目录结构规范参见<a href="../basics/structure.html">目录结构</a>。</p>
<p>好，现在可以启动应用来体验下</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm run dev</span><br><span class="line">$ open localhost:7001</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意：</p>
<ul>
<li>Controller 有 <code>class</code> 和 <code>exports</code> 两种编写方式，本文示范的是前者，你可能需要参考 <a href="../basics/controller.html">Controller</a> 文档。</li>
<li>Config 也有 <code>module.exports</code> 和 <code>exports</code> 的写法，具体参考 <a href="https://nodejs.org/api/modules.html#modules_exports_shortcut" target="_blank" rel="noopener">Node.js modules 文档</a>。</li>
</ul>
</blockquote>
<h3 id="静态资源"><a class="markdown-anchor" href="#静态资源">#</a> 静态资源</h3>
<p>Egg 内置了 <a href="https://github.com/eggjs/egg-static" target="_blank" rel="noopener">static</a> 插件，线上环境建议部署到 CDN，无需该插件。</p>
<p>static 插件默认映射 <code>/public/* -&gt; app/public/*</code> 目录</p>
<p>此处，我们把静态资源都放到 <code>app/public</code> 目录即可：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">app/public</span><br><span class="line">├── css</span><br><span class="line">│   └── news.css</span><br><span class="line">└── js</span><br><span class="line">    ├── lib.js</span><br><span class="line">    └── news.js</span><br></pre></td></tr></table></figure>
<h3 id="模板渲染"><a class="markdown-anchor" href="#模板渲染">#</a> 模板渲染</h3>
<p>绝大多数情况，我们都需要读取数据后渲染模板，然后呈现给用户。故我们需要引入对应的模板引擎。</p>
<p>框架并不强制你使用某种模板引擎，只是约定了 <a href="../advanced/view-plugin.html">View 插件开发规范</a>，开发者可以引入不同的插件来实现差异化定制。</p>
<p>更多用法参见 <a href="../core/view.html">View</a>。</p>
<p>在本例中，我们使用 <a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener">Nunjucks</a> 来渲染，先安装对应的插件 <a href="https://github.com/eggjs/egg-view-nunjucks" target="_blank" rel="noopener">egg-view-nunjucks</a> ：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i egg-view-nunjucks --save</span><br></pre></td></tr></table></figure>
<p>开启插件：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/plugin.js</span></span><br><span class="line">exports.nunjucks = &#123;</span><br><span class="line">  enable: <span class="literal">true</span>,</span><br><span class="line">  package: <span class="string">'egg-view-nunjucks'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.keys = <span class="xml"><span class="tag">&lt;<span class="name">此处改为你自己的</span> <span class="attr">Cookie</span> 安全字符串&gt;</span>;</span></span><br><span class="line"><span class="xml">// 添加 view 配置</span></span><br><span class="line"><span class="xml">exports.view = &#123;</span></span><br><span class="line"><span class="xml">  defaultViewEngine: 'nunjucks',</span></span><br><span class="line"><span class="xml">  mapping: &#123;</span></span><br><span class="line"><span class="xml">    '.tpl': 'nunjucks',</span></span><br><span class="line"><span class="xml">  &#125;,</span></span><br><span class="line"><span class="xml">&#125;;</span></span><br></pre></td></tr></table></figure>
<p><strong>注意：是 <code>config</code> 目录，不是 <code>app/config</code>!</strong></p>
<p>为列表页编写模板文件，一般放置在 <code>app/view</code> 目录下</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- app/view/news/list.tpl --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hacker News<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"/public/css/news.css"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span> <span class="attr">class</span>=<span class="string">"news-view view"</span>&gt;</span></span><br><span class="line">      &#123;% for item in list %&#125;</span><br><span class="line">        <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">"item"</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"&#123;&#123; item.url &#125;&#125;"</span>&gt;</span>&#123;&#123; item.title &#125;&#125;<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      &#123;% endfor %&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>添加 Controller 和 Router</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/news.js</span></span><br><span class="line"><span class="keyword">const</span> Controller = <span class="built_in">require</span>(<span class="string">'egg'</span>).Controller;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NewsController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> list() &#123;</span><br><span class="line">    <span class="keyword">const</span> dataList = &#123;</span><br><span class="line">      list: [</span><br><span class="line">        &#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">title</span>: <span class="string">'this is news 1'</span>, <span class="attr">url</span>: <span class="string">'/news/1'</span> &#125;,</span><br><span class="line">        &#123; <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">title</span>: <span class="string">'this is news 2'</span>, <span class="attr">url</span>: <span class="string">'/news/2'</span> &#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">await</span> <span class="keyword">this</span>.ctx.render(<span class="string">'news/list.tpl'</span>, dataList);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = NewsController;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app/router.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; router, controller &#125; = app;</span><br><span class="line">  router.get(<span class="string">'/'</span>, controller.home.index);</span><br><span class="line">  router.get(<span class="string">'/news'</span>, controller.news.list);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>启动浏览器，访问 http://localhost:7001/news 即可看到渲染后的页面。</p>
<p><strong>提示：开发期默认开启了 <a href="https://github.com/eggjs/egg-development" target="_blank" rel="noopener">development</a> 插件，修改后端代码后，会自动重启 Worker 进程。</strong></p>
<h3 id="编写-service"><a class="markdown-anchor" href="#编写-service">#</a> 编写 service</h3>
<p>在实际应用中，Controller 一般不会自己产出数据，也不会包含复杂的逻辑，复杂的过程应抽象为业务逻辑层 <a href="../basics/service.html">Service</a>。</p>
<p>我们来添加一个 Service 抓取 <a href="https://github.com/HackerNews/API" target="_blank" rel="noopener">Hacker News</a> 的数据 ，如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/service/news.js</span></span><br><span class="line"><span class="keyword">const</span> Service = <span class="built_in">require</span>(<span class="string">'egg'</span>).Service;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NewsService</span> <span class="keyword">extends</span> <span class="title">Service</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> list(page = <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="comment">// read config</span></span><br><span class="line">    <span class="keyword">const</span> &#123; serverUrl, pageSize &#125; = <span class="keyword">this</span>.config.news;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// use build-in http client to GET hacker-news api</span></span><br><span class="line">    <span class="keyword">const</span> &#123; <span class="attr">data</span>: idList &#125; = <span class="keyword">await</span> <span class="keyword">this</span>.ctx.curl(<span class="string">`<span class="subst">$&#123;serverUrl&#125;</span>/topstories.json`</span>, &#123;</span><br><span class="line">      data: &#123;</span><br><span class="line">        orderBy: <span class="string">'"$key"'</span>,</span><br><span class="line">        startAt: <span class="string">`"<span class="subst">$&#123;pageSize * (page - <span class="number">1</span>)&#125;</span>"`</span>,</span><br><span class="line">        endAt: <span class="string">`"<span class="subst">$&#123;pageSize * page - <span class="number">1</span>&#125;</span>"`</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// parallel GET detail</span></span><br><span class="line">    <span class="keyword">const</span> newsList = <span class="keyword">await</span> <span class="built_in">Promise</span>.all(</span><br><span class="line">      <span class="built_in">Object</span>.keys(idList).map(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;serverUrl&#125;</span>/item/<span class="subst">$&#123;idList[key]&#125;</span>.json`</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.ctx.curl(url, &#123; <span class="attr">dataType</span>: <span class="string">'json'</span> &#125;);</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">return</span> newsList.map(<span class="function"><span class="params">res</span> =&gt;</span> res.data);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = NewsService;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>框架提供了内置的 <a href="../core/httpclient.html">HttpClient</a> 来方便开发者使用 HTTP 请求。</p>
</blockquote>
<p>然后稍微修改下之前的 Controller：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/controller/news.js</span></span><br><span class="line"><span class="keyword">const</span> Controller = <span class="built_in">require</span>(<span class="string">'egg'</span>).Controller;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NewsController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> list() &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="keyword">this</span>.ctx;</span><br><span class="line">    <span class="keyword">const</span> page = ctx.query.page || <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">const</span> newsList = <span class="keyword">await</span> ctx.service.news.list(page);</span><br><span class="line">    <span class="keyword">await</span> ctx.render(<span class="string">'news/list.tpl'</span>, &#123; <span class="attr">list</span>: newsList &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = NewsController;</span><br></pre></td></tr></table></figure>
<p>还需增加 <code>app/service/news.js</code> 中读取到的配置：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="comment">// 添加 news 的配置项</span></span><br><span class="line">exports.news = &#123;</span><br><span class="line">  pageSize: <span class="number">5</span>,</span><br><span class="line">  serverUrl: <span class="string">'https://hacker-news.firebaseio.com/v0'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="编写扩展"><a class="markdown-anchor" href="#编写扩展">#</a> 编写扩展</h3>
<p>遇到一个小问题，我们的资讯时间的数据是 UnixTime 格式的，我们希望显示为便于阅读的格式。</p>
<p>框架提供了一种快速扩展的方式，只需在 <code>app/extend</code> 目录下提供扩展脚本即可，具体参见<a href="../basics/extend.html">扩展</a>。</p>
<p>在这里，我们可以使用 View 插件支持的 Helper 来实现：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i moment --save</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/helper.js</span></span><br><span class="line"><span class="keyword">const</span> moment = <span class="built_in">require</span>(<span class="string">'moment'</span>);</span><br><span class="line">exports.relativeTime = <span class="function"><span class="params">time</span> =&gt;</span> moment(<span class="keyword">new</span> <span class="built_in">Date</span>(time * <span class="number">1000</span>)).fromNow();</span><br></pre></td></tr></table></figure>
<p>在模板里面使用：</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- app/view/news/list.tpl --&gt;</span></span><br><span class="line">&#123;&#123; helper.relativeTime(item.time) &#125;&#125;</span><br></pre></td></tr></table></figure>
<h3 id="编写-middleware"><a class="markdown-anchor" href="#编写-middleware">#</a> 编写 Middleware</h3>
<p>假设有个需求：我们的新闻站点，禁止百度爬虫访问。</p>
<p>聪明的同学们一定很快能想到可以通过 <a href="../basics/middleware.html">Middleware</a> 判断 User-Agent，如下：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/middleware/robot.js</span></span><br><span class="line"><span class="comment">// options === app.config.robot</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function">(<span class="params">options, app</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">robotMiddleware</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> source = ctx.get(<span class="string">'user-agent'</span>) || <span class="string">''</span>;</span><br><span class="line">    <span class="keyword">const</span> match = options.ua.some(<span class="function"><span class="params">ua</span> =&gt;</span> ua.test(source));</span><br><span class="line">    <span class="keyword">if</span> (match) &#123;</span><br><span class="line">      ctx.status = <span class="number">403</span>;</span><br><span class="line">      ctx.message = <span class="string">'Go away, robot.'</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">await</span> next();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="comment">// add middleware robot</span></span><br><span class="line">exports.middleware = [</span><br><span class="line">  <span class="string">'robot'</span></span><br><span class="line">];</span><br><span class="line"><span class="comment">// robot's configurations</span></span><br><span class="line">exports.robot = &#123;</span><br><span class="line">  ua: [</span><br><span class="line">    /Baiduspider/i,</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>现在可以使用 <code>curl http://localhost:7001/news -A &quot;Baiduspider&quot;</code> 看看效果。</p>
<p>更多参见<a href="../basics/middleware.html">中间件</a>文档。</p>
<h3 id="配置文件"><a class="markdown-anchor" href="#配置文件">#</a> 配置文件</h3>
<p>写业务的时候，不可避免的需要有配置文件，框架提供了强大的配置合并管理功能：</p>
<ul>
<li>支持按环境变量加载不同的配置文件，如 <code>config.local.js</code>， <code>config.prod.js</code> 等等。</li>
<li>应用/插件/框架都可以配置自己的配置文件，框架将按顺序合并加载。</li>
<li>具体合并逻辑可参见<a href="../basics/config.html#%E9%85%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%E9%A1%BA%E5%BA%8F">配置文件</a>。</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.robot = &#123;</span><br><span class="line">  ua: [</span><br><span class="line">    /curl/i,</span><br><span class="line">    /Baiduspider/i,</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// config/config.local.js</span></span><br><span class="line"><span class="comment">// only read at development mode, will override default</span></span><br><span class="line">exports.robot = &#123;</span><br><span class="line">  ua: [</span><br><span class="line">    /Baiduspider/i,</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app/service/some.js</span></span><br><span class="line"><span class="keyword">const</span> Service = <span class="built_in">require</span>(<span class="string">'egg'</span>).Service;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SomeService</span> <span class="keyword">extends</span> <span class="title">Service</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> list() &#123;</span><br><span class="line">    <span class="keyword">const</span> rule = <span class="keyword">this</span>.config.robot.ua;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = SomeService;</span><br></pre></td></tr></table></figure>
<h3 id="单元测试"><a class="markdown-anchor" href="#单元测试">#</a> 单元测试</h3>
<p>单元测试非常重要，框架也提供了 <a href="https://github.com/eggjs/egg-bin" target="_blank" rel="noopener">egg-bin</a> 来帮开发者无痛的编写测试。</p>
<p>测试文件应该放在项目根目录下的 test 目录下，并以 <code>test.js</code> 为后缀名，即 <code>{app_root}/test/**/*.test.js</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// test/app/middleware/robot.test.js</span></span><br><span class="line"><span class="keyword">const</span> &#123; app, mock, assert &#125; = <span class="built_in">require</span>(<span class="string">'egg-mock/bootstrap'</span>);</span><br><span class="line"></span><br><span class="line">describe(<span class="string">'test/app/middleware/robot.test.js'</span>, () =&gt; &#123;</span><br><span class="line">  it(<span class="string">'should block robot'</span>, () =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> app.httpRequest()</span><br><span class="line">      .get(<span class="string">'/'</span>)</span><br><span class="line">      .set(<span class="string">'User-Agent'</span>, <span class="string">"Baiduspider"</span>)</span><br><span class="line">      .expect(<span class="number">403</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>然后配置依赖和 <code>npm scripts</code>：</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"scripts"</span>: &#123;</span><br><span class="line">    <span class="attr">"test"</span>: <span class="string">"egg-bin test"</span>,</span><br><span class="line">    <span class="attr">"cov"</span>: <span class="string">"egg-bin cov"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i egg-mock --save-dev</span><br></pre></td></tr></table></figure>
<p>执行测试：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm <span class="built_in">test</span></span><br></pre></td></tr></table></figure>
<p>就这么简单，更多请参见 <a href="../core/unittest.html">单元测试</a>。</p>
<h2 id="后记"><a class="markdown-anchor" href="#后记">#</a> 后记</h2>
<p>短短几章内容，只能讲 Egg 的冰山一角，我们建议开发者继续阅读其他章节：</p>
<ul>
<li>关于骨架类型，参见<a href="../tutorials/index.html">骨架说明</a></li>
<li>提供了强大的扩展机制，参见<a href="../basics/plugin.html">插件</a>。</li>
<li>一个大规模的团队需要遵循一定的约束和约定，在 Egg 里我们建议封装适合自己团队的上层框架，参见 <a href="../advanced/framework.html">框架开发</a>。</li>
<li>这是一个渐进式的框架，代码的共建，复用和下沉，竟然可以这么的无痛，建议阅读 <a href="../tutorials/progressive.html">渐进式开发</a>。</li>
<li>写单元测试其实很简单的事，Egg 也提供了非常多的配套辅助，我们强烈建议大家测试驱动开发，具体参见 <a href="../core/unittest.html">单元测试</a>。</li>
</ul>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/intro/quickstart.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/intro/quickstart.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
